﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Threading;

namespace NTasking.Internals
{

    /// <summary>
    /// Implements the methods of <see cref="ThreadWrapper"/> linked to abortion and abortion protection.
    /// </summary>
    internal partial class ThreadWrapper
    {

        #region Fields

        /// <summary>
        /// A lock used to prevent against concurrent access to the abortion related methods
        /// </summary>
        private readonly object _abortionLock = new object();


        private readonly CodeWrapper _noAbort;


        private readonly CodeWrapper _withAbort;

        #endregion Fields


        #region Internal Classes

        /// <summary>
        /// Define the root of all classes used to associate data to the thread abortion
        /// </summary>
        private abstract class TaskAbortDataBase
        {
        }


        /// <summary>
        /// Define the data which is passed along with a normal thread abortion
        /// </summary>
        private class TaskAbortData : TaskAbortDataBase
        {
        }


        /// <summary>
        /// Define the data which is passed along with the abortion of an abortable part
        /// </summary>
        private class TaskInterruptData : TaskAbortDataBase
        {

            internal readonly MethodCall InterruptedCall;

            internal TaskInterruptData(MethodCall interruptedCall)
            {
                InterruptedCall = interruptedCall;
            }
        }


        /// <summary>
        /// Define the data which is passed along with the abortion used to implement the terminate statement
        /// </summary>
        private class TaskTerminateData : TaskAbortDataBase
        {
        }


        /// <summary>
        /// Define the data which is passed along with the abortion used to implement the requeue statement
        /// </summary>
        private class TaskRequeuedData : TaskAbortDataBase
        {

            /// <summary>
            /// Gets or sets the entry on which the call is requeued.
            /// </summary>
            /// <value>The entry on which the call is requeued.</value>
            internal ExecutionContext Requeued { get; private set; }


            /// <summary>
            /// Initializes a new instance of the <see cref="TaskRequeuedData"/> class.
            /// </summary>
            /// <param name="requeued">The entry on which the call is requeued.</param>
            internal TaskRequeuedData(ExecutionContext requeued)
            {
                Requeued = requeued;
            }

        }

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets a value indicating that an abort request has been performed while the thread was
        /// in an abort deferred operation.
        /// </summary>
        /// <value>The pending abort request has been performed, or <c>null</c> if no abort request is pending.</value>
        private TaskAbortDataBase PendingAbort { get; set; }


        /// <summary>
        /// Gets a value indicating whether this <see cref="ThreadWrapper"/> is abortable.
        /// </summary>
        /// <value><c>true</c> if the thread abortable; otherwise, <c>false</c>.</value>
        internal bool Abortable
        {
            get { return AbortDeferredCount == 0; }
        }


        /// <summary>
        /// Gets or sets a value indicating whether a concurrent execution of the TryAbort method is already in progress.
        /// </summary>
        /// <value><c>true</c> if a call of the TryAbort method is already in progress; otherwise, <c>false</c>.</value>
        private bool TryAbortInProgress { get; set; }


        /// <summary>
        /// Gets or sets a value which permits to track the number of calls to AbortDefer for which a corresponding
        /// call to AbortResume has not been issued.
        /// </summary>
        /// <value>The abort count of calls to AbortDefer.</value>
        private int AbortDeferredCount { get; set; }


        /// <summary>
        /// Gets a value indicating whether abortion of the thread that this instance encapsulates has been requested.
        /// </summary>
        /// <value><c>true</c> if abortion of the thread has been requested; otherwise, <c>false</c>.</value>
        internal bool AbortRequested
        {
            get { return (Thread.ThreadState & ThreadState.AbortRequested) != 0; }
        }


        /// <summary>
        /// Gets or sets a value indicating whether abort requests should be ignored because the task is completed.
        /// </summary>
        /// <value><c>true</c> if abort requests should be ignored; otherwise, <c>false</c>.</value>
        internal bool IgnoreFurtherAborts { get; set; }


        /// <summary>
        /// Gets or sets a value indicating whether abortion of this instance has been requested.
        /// </summary>
        /// <value>
        /// 	<c>true</c> abortion of this instance has been requested; otherwise, <c>false</c>.
        /// </value>
        internal bool IsAborting { get; private set; }

        #endregion


        #region Static Methods

        /// <summary>
        /// Determines whether the given <see cref="ThreadAbortException"/> is an illegal abort
        /// perfomed through Thread.Abort rather than Task.Abort.
        /// </summary>
        /// <param name="ex">The exception to be checked.</param>
        /// <returns>
        /// 	<c>true</c> if the exception represents an illegal abort; otherwise, <c>false</c>.
        /// </returns>
        internal static bool IsIllegalAbort(ThreadAbortException ex)
        {
            return ex.ExceptionState as TaskAbortDataBase == null;
        }


        /// <summary>
        /// Determines whether the given <see cref="ThreadAbortException"/> is an actual abort.
        /// </summary>
        /// <param name="ex">The exception to be checked.</param>
        /// <returns>
        /// 	<c>true</c> if the exception represents an actual abort; otherwise, <c>false</c>.
        /// </returns>
        internal static bool IsTaskAbort(ThreadAbortException ex)
        {
            return ex.ExceptionState is TaskAbortData;
        }


        /// <summary>
        /// Determines whether the given <see cref="ThreadAbortException"/> is a task termination.
        /// </summary>
        /// <param name="ex">The exception to be checked.</param>
        /// <returns>
        /// 	<c>true</c> if the exception represents a task termination; otherwise, <c>false</c>.
        /// </returns>
        internal static bool IsTaskTerminate(ThreadAbortException ex)
        {
            return ex.ExceptionState is TaskTerminateData;
        }


        /// <summary>
        /// Determines whether the given <see cref="ThreadAbortException"/> is an interruption
        /// of the abortable part of the given call.
        /// </summary>
        /// <param name="ex">The exception to be checked.</param>
        /// <param name="call">The call which abortable part is interrupted.</param>
        /// <returns>
        ///     <c>true</c> if the exception represents an abortable part interruption; otherwise, <c>false</c>.
        /// </returns>
        internal static bool IsTaskInterrupt(ThreadAbortException ex, MethodCall call)
        {
            TaskInterruptData data = ex.ExceptionState as TaskInterruptData;

            return data != null && data.InterruptedCall == call;
        }


        /// <summary>
        /// Determines whether the given <see cref="ThreadAbortException"/> is a requeue.
        /// </summary>
        /// <param name="ex">The exception to be checked.</param>
        /// <returns>
        /// 	<c>true</c> if the exception represents a requeue; otherwise, <c>false</c>.
        /// </returns>
        internal static bool IsTaskRequeue(ThreadAbortException ex)
        {
            return ex.ExceptionState is TaskRequeuedData;
        }


        /// <summary>
        /// Get the target entry of a requeue represented by the given exception.
        /// </summary>
        /// <param name="ex">The exception representing the requeue.</param>
        /// <returns>The requeue target entry</returns>
        internal static ExecutionContext RequeueTarget(ThreadAbortException ex)
        {
            return ((TaskRequeuedData)ex.ExceptionState).Requeued;
        }


        /// <summary>
        /// Cancels an Abort request for the current thread.
        /// </summary>
        internal static void ResetAbort()
        {
            Thread.ResetAbort();
        }

        #endregion


        #region Methods

        /// <summary>
        /// Prevent abortion of the current thread.
        /// </summary>
        internal void AbortDefer()
        {
            lock (_abortionLock)
            {
                AbortDeferredCount += 1;
                Tracer.Trace("Task " + this + " is not abortable count=" + AbortDeferredCount);
            }
        }


        /// <summary>
        /// Enable abortion of the current thread.
        /// </summary>
        internal void AbortResume()
        {
            lock (_abortionLock)
            {
#if TASKING_DEBUG
                if (AbortDeferredCount == 0)
                    Errors.FatalError("Calling " + this + ".AbortResume when count=0");
#endif

                AbortDeferredCount -= 1;

                if (Abortable)
                {
                    Tracer.Trace("Task " + this + " is abortable");

                    if (PendingAbort != null)
                    {
                        Tracer.Trace("   Task " + this + " had pending Abort");

                        EffectivelyAbort(PendingAbort);
                    }
                }
                else
                    Tracer.Trace("Task " + this + " is not abortable count=" + AbortDeferredCount);
            }
        }


        /// <summary>
        /// Provides a new <see cref="CodeWrapper"/> block inside which the task is protected against abortion
        /// </summary>
        /// <returns></returns>
        internal CodeWrapper NoAbort()
        {
            return _noAbort.Enter();
        }


        /// <summary>
        /// Provides a new <see cref="CodeWrapper"/> block inside which the task can be aborted
        /// </summary>
        /// <returns></returns>
        internal CodeWrapper WithAbort()
        {
            return _withAbort.Enter();
        }


        /// <summary>
        /// Aborts this instance.
        /// </summary>
        public void Abort()
        {
            Abort(false, null);
        }


        /// <summary>
        /// Aborts the current instance.
        /// </summary>
        /// <param name="inactive">A boolean indicating whether the current instance is still inactive.</param>
        /// <param name="threadFinish">A cleanup action to be executed if the current instance is still incactive</param>
        internal void Abort(bool inactive, Action threadFinish)
        {
            // Check that this instance can be blocked since abort is an Ada
            // 'potentially blocking operation'
            if (!CurrentThread.CanBlock)
                throw Errors.PotentiallyBlockingOperationNotAllowed("Task abortion");

            // This method can't be implemented by putting everything in a finally block since a task is
            // entitled to suicide by aborting itself. In this case the ThreadAbortException is thrown
            // immediately, even within a finally block

            using (CurrentThread.NoAbort())
            {
                Tracer.Trace("Aborting " + this);

                TryAbort(inactive, threadFinish, new TaskAbortData());
            }
        }


        /// <summary>
        /// Interrupts the abortable part of the specified call.
        /// </summary>
        /// <param name="call">The call which .</param>
        internal void Interrupt(MethodCall call)
        {
            Tracer.Trace("Interrupting abortable part of " + this);
            TryAbort(false, null, new TaskInterruptData(call));
        }


        /// <summary>
        /// Terminates this instance.
        /// </summary>
        internal void Terminate()
        {
            try { }
            finally
            {
                // Do everything in a finally to protect against abortion of executing task

                Tracer.Trace("Terminating " + this);
                Thread.Abort(new TaskTerminateData());
            }
        }


        /// <summary>
        /// Requeues this instance onto the specified target.
        /// </summary>
        /// <param name="target">The target entry.</param>
        internal static void Requeue(ExecutionContext target)
        {
            //throw new RequeueError(target);
            CurrentThread.Thread.Abort(new TaskRequeuedData(target));
        }


        /// <summary>
        /// Try to abort this instance. If this instance is not abortable the abort request will be queued for later.
        /// </summary>
        /// <param name="inactive">A boolean indicating whether the current instance is still inactive.</param>
        /// <param name="threadFinish">A cleanup action to be executed if the current instance is still incactive</param>
        /// <param name="data">The data representing the kind of abort which should be performed.</param>
        private void TryAbort(bool inactive, Action threadFinish, TaskAbortDataBase data)
        {
            if (TryAbortInProgress)
                return;

            // To protect against concurrent aborts
            TryAbortInProgress = true;

            lock (_abortionLock)
            {
                if (inactive)
                {
                    Tracer.Trace("   This is an abort of unstarted task " + this);

                    IsAborting = true;
                    if (threadFinish != null)
                        threadFinish();
                }
                else if (!Abortable)
                {
                    Tracer.Trace("   Task not abortable, abort request queued for " + this);

                    if (PendingAbort == null || PendingAbort is TaskInterruptData)
                        PendingAbort = data;
                    IsAborting = !(PendingAbort is TaskInterruptData);
                }
                else
                {
                    EffectivelyAbort(data);
                }
            }

            TryAbortInProgress = false;
        }


        /// <summary>
        /// Effectively abort this instance.
        /// </summary>
        /// <param name="data">The data representing the kind of abort which should be performed.</param>
        private void EffectivelyAbort(TaskAbortDataBase data)
        {
            PendingAbort = null;

            if (!(data is TaskInterruptData))
                AbortDependents();

            if (IgnoreFurtherAborts)
            {
                Tracer.Trace("   Ignoring abort during completion of " + this);
            }
            else
            {
                IsAborting = !(data is TaskInterruptData);

                if (IsAborting)
                    Tracer.Trace("   This is a normal abort of " + this);
                else
                    Tracer.Trace("   This is an interruption of an abortable part of " + this);

                try
                {
                    Monitor.Exit(_abortionLock);
                    Thread.Abort(data);
                }
                finally
                {
                    Monitor.Enter(_abortionLock);
                }
                Tracer.Trace("   Sent abort signal to " + this);
            }
        }


        /// <summary>
        /// Aborts all the threads which are dependents of this instance.
        /// </summary>
        private void AbortDependents()
        {
            Tracer.Trace("   Aborting dependent tasks of " + this);
            Block[] blocks = _blockStack.ToArray();

            foreach (Block block in blocks)
                block.AbortDependentTasks();
        }

        #endregion

    }

}
